#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SIZE 1024
#define SEP " "
#define MAX_ARGC 64

#define STREND '\0'
#define NoneRedir -1  // 没有重定向
#define StdinRedir 0  // 输入重定向
#define StdoutRedir 1 // 输出重定向
#define AppendRedir 2 // 追加重定向

int redir_type = NoneRedir; // 默认没有重定向
char *filename = NULL;

char *argv[MAX_ARGC]; // 指针数组，用来放分割后的每个小字符串
char pwd[SIZE];
int lastcode = 0;

const char *HostName()
{
    char *hostname = getenv("HOSTNAME");
    if (hostname)
        return hostname;
    else
        return "None";
}
const char *Home()
{
    return getenv("HOME");
}
const char *UserName()
{
    char *name = getenv("USER");
    if (name)
        return name;
    else
        return "None";
}
const char *CurrentWorkDir()
{
    char *name = getenv("PWD");
    if (name)
        return name;
    else
        return "None";
}
int Interactive(char out[], int size)
{
    printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());

    fgets(out, size, stdin);

    out[strlen(out) - 1] = 0;
    // fgets()会接收回车，处理一下：去掉 '\n'
    //  不用担心越界问题，因为至少会输入一个回车

    return strlen(out);
}

#define IgnSpace(arr, pos)      \
    do                          \
    {                           \
        while (arr[pos] == ' ') \
            pos++;              \
    } while (0)

void CheckRedir(char in[])
{
    // ls -a -l
    // ls -a -l > log.txt
    // ls -a -l >>log.txt
    // cat < log.txt

    redir_type = NoneRedir; // 每次进来都重新初始化
    filename = NULL;

    int pos = strlen(in) - 1;
    while (pos >= 0)
    {
        if (in[pos] == '>') // 有一个 >
        {
            if (in[pos - 1] == '>') // 有两个 >
            {
                redir_type = AppendRedir;
                in[pos - 1] = STREND;
            }
            else
            {
                redir_type = StdoutRedir;
                in[pos] = STREND;
            }
            pos++;
            IgnSpace(in, pos);
            filename = in + pos;
            break;
        }
        else if (in[pos] == '<')
        {
            redir_type = StdinRedir;
            in[pos] = STREND;
            IgnSpace(in, pos);
            filename = in + pos;
            break;
        }
        --pos;
    }
}
void Split(char in[])
{
    // 检测是否有重定向
    CheckRedir(in);

    int i = 0;
    argv[i++] = strtok(in, SEP);
    while (argv[i++] = strtok(NULL, SEP))
        ; // 一直循环，直到分割完,最后一次 strtok 返回 NULL，正好做argv的结尾
}
void Excute()
{
    pid_t id = fork(); // 创建子进程
    if (id == 0)
    {
        int fd = -1;
        if (redir_type == StdinRedir) // 输入重定向
        {
            fd = open(filename, O_RDONLY | O_CREAT | O_TRUNC, 0666);
            dup2(fd, 0);
        }
        else if (redir_type == StdoutRedir) // 输出重定向
        {
            fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
            dup2(fd, 1);
        }
        else if (redir_type == AppendRedir) // 追加重定向
        {
            fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
            dup2(fd, 1);
        }
        else
        {
            // 什么都不做
        }
        execvp(argv[0], argv); // 进程替换
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0); // 等待子进程执行完毕
    if (rid > 0)
    {
        // 得到退出码
        lastcode = WEXITSTATUS(status);
    }
}

int BuildinCmd()
{
    int ret = 0; // 是否为内键命令，是：1，否：0

    // cd 命令： 直接 cd 是返回家目录，cd XXX 就是跳到 XXX处
    if (strcmp("cd", argv[0]) == 0)
    {
        ret = 1;
        const char *target = argv[1];

        // 更改当前工作路径
        if (!target) // 直接 cd ，返回家目录
            target = Home();

        chdir(target); // 直接更改工作路径到target

        // 手动更新环境变量 PWD
        char temp[1024];
        getcwd(temp, 1024);

        snprintf(pwd, SIZE, "PWD=%s", temp);
        putenv(pwd);
    }
    // echo 命令：echo   echo $?    echo $HOME  echo abcdef
    else if (strcmp("echo", argv[0]) == 0)
    {
        ret = 1;
        if (argv[1] == NULL)
            printf("\n");
        else
        {
            if (argv[1][0] == '$')
            {
                if (argv[1][1] == '?')
                {
                    printf("%d\n", lastcode);
                    lastcode = 0;
                }
                else
                {
                    char *e = getenv(argv[1] + 1);
                    if (e)
                        printf("%s\n", e);
                }
            }
            else
            {
                for (int i = 1; argv[i]; i++)
                {
                    printf("%s ", argv[i]);
                }
                printf("\n");
            }
        }
    }
    else if (strcmp("export", argv[0]) == 0)
    {
        ret = 1;
        if (argv[1])
            putenv(argv[1]);
    }
    return ret;
}
int main()
{
    char commandline[SIZE];

    while (1)
    {
        // 打印命令行提示符，fgets() 获取输入的命令
        int n = Interactive(commandline, SIZE);
        if (!n)
            continue;

        // 对输入的命令分割
        Split(commandline);

        // 处理内键命令
        n = BuildinCmd();
        if (n)
            continue;

        // 执行命令
        Excute();
    }
}
//
